package com.ruoyi.workflow.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.system.service.ISysDictDataService;
import com.ruoyi.workflow.domain.BpmnInfo;
import com.ruoyi.workflow.domain.BpmnNode;
import com.ruoyi.workflow.mapper.BpmnInfoMapper;
import com.ruoyi.workflow.service.IBpmnInfoService;
import com.ruoyi.workflow.service.IBpmnNodeService;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.zip.ZipInputStream;

import static com.ruoyi.common.constant.FileConstants.FILE_TYPE_BPMN;
import static com.ruoyi.common.constant.FileConstants.FILE_TYPE_IMAGE;

/**
 * 流程定义信息Service业务层处理
 *
 * @author shier
 * @date 2022-11-14
 */
@Service
public class BpmnInfoServiceImpl extends ServiceImpl<BpmnInfoMapper, BpmnInfo> implements IBpmnInfoService {

    private final IBpmnNodeService        bpmnNodeService;
    private final ProcessDiagramGenerator processDiagramGenerator;
    private final ISysDictDataService     sysDictDataService;
    private final RepositoryService       repositoryService;
    public BpmnInfoServiceImpl(RepositoryService repositoryService, ISysDictDataService sysDictDataService, ProcessDiagramGenerator processDiagramGenerator, IBpmnNodeService bpmnNodeService) {
        this.repositoryService       = repositoryService;
        this.sysDictDataService      = sysDictDataService;
        this.processDiagramGenerator = processDiagramGenerator;
        this.bpmnNodeService         = bpmnNodeService;
    }

    /**
     * 查询流程定义信息列表
     *
     * @param bpmnInfo 流程定义信息
     * @return 流程定义信息
     */
    @Override
    public List<BpmnInfo> selectBpmnInfoList(BpmnInfo bpmnInfo) {
        return getBaseMapper().selectBpmnInfoList(bpmnInfo);
    }

    @Override
    public InputStream getResourceByType(Long id, String type) {
        // 1: 基于 bpmnInfo的id 查询 bpmnInfo对象
        BpmnInfo bpmnInfo = this.getById(id);
        // 1.5: 获取 部署id
        String deploymentId = bpmnInfo.getDeploymentId();

        // 2: 基于 流程部署id(在 bpmnInfo对象里) 查询 流程定义对象
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploymentId)
                .singleResult();

        // 3: 判断当前要查询的数据的类型
        // 4: 如果当前类型是是 image, 就查询流程图
        if (FILE_TYPE_IMAGE.equals(type)) {
            // 4.0: 获取通过 流程定义对象 的图片资源名称
            String resourceName = processDefinition.getDiagramResourceName();
            // 4.1: 判断是否有图片资源
            if (StringUtils.isNotEmpty(resourceName)) {
                // 4.2:果有图片直接通过 repositoryService 获取图片流对象并返回
                return repositoryService.getResourceAsStream(deploymentId, resourceName);
            } else {
                // 4.3: 获取 BPMN.xml 模型对象
                BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
                // 4.4: 通过流程图生成工具,生成流程,得到流对象并返回
                return processDiagramGenerator.generateDiagram(bpmnModel, "宋体", "宋体", "宋体");
            }
        } else if(FILE_TYPE_BPMN.equals(type)) {
            // 5: 如果类型是 bpmn, 直接通过 repositoryService 获取文件流对象并返回
            return repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
        }
        // 6: 如果是其他类型, 直接报错提示不支持的类型
        throw new ServiceException("当前查看的文件类型不支持");
    }

    /**
     * 批量删除流程定义信息
     *
     * @param ids 需要删除的流程定义信息主键
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteBpmnInfoByIds(Long[] ids) {
        for (Long id : ids) {
            // 1: 基于 bpmnInfo的id 查询到 bpmnInfo对象
            BpmnInfo bpmnInfo = this.getById(id);

            try {
                // 2: 基于 bpmnInfo对象中的部署id, 调用 activiti 中的 API 来删除部署信息
                repositoryService.deleteDeployment(bpmnInfo.getDeploymentId());
            } catch (Exception e) {
                e.printStackTrace();
                // 3: 如果删除失败, 提示前段有正在进行中的流程,无法删除
                throw new ServiceException("删除失败, 当前有正在进行的流程定义,不能被删除!!!");
            }

            // 4: 删除 流程节点
            List<BpmnNode> bpmnNodeList = bpmnNodeService.getBaseMapper().selectList(Wrappers.lambdaQuery(BpmnNode.class).eq(BpmnNode::getBpmnInfoId, id));
            bpmnNodeService.removeBatchByIds(bpmnNodeList);
            // 5: 删除 流程节点与用户 中间表
            for (BpmnNode bpmnNode : bpmnNodeList) {
                bpmnNodeService.deleteRelation(bpmnNode.getId());
            }

        }
        // 4: 批量删除 bpmnInfo 的数据
        return super.removeBatchByIds(Arrays.asList(ids));
    }


    // 开启事务: 回滚的类型为 Exception; 默认为 RuntimeException
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deploy(BpmnInfo bpmnInfo, InputStream fileInputStream, String dictType) {
        // 1: 获取流程定义的类型标签
        // 1.1: 前段把数据字典的类型传过来 => 通过 字典的类型 和 字典的value 查询 字典的Label(展示的中文)
        String dictLabel = sysDictDataService.selectDictLabel(dictType, bpmnInfo.getBpmnType());
        // 1.2: 把 dictLabel 设置进 bpmnInfo 对象
        bpmnInfo.setBpmnLabel(dictLabel);

        // 2. 调用 activiti 进行流程部署
        Deployment deployment;
        String suffix = FileUtils.getFileSuffix(bpmnInfo.getFileName());

        if ("zip".equals(suffix)) {
            deployment = repositoryService.createDeployment()
                    .addZipInputStream(new ZipInputStream(fileInputStream)) // 上传 zip 格式的文件
                    .name(bpmnInfo.getBpmnLabel())
                    .deploy();
        } else {
            deployment = repositoryService.createDeployment()
                    .addInputStream(bpmnInfo.getFileName(), fileInputStream)
                    .name(bpmnInfo.getBpmnLabel())
                    .deploy();
        }

        // 2.5: 保存部署后的相关属性
        //      部署ID
        bpmnInfo.setDeploymentId(deployment.getId());
        //      部署时间
        bpmnInfo.setDeployTime(deployment.getDeploymentTime());
        //      流程定义 Key => 这个 Key 四约定好和 字典数据的 value 一致
        String processDefinitionKey = bpmnInfo.getBpmnType();
        bpmnInfo.setProcessDefinitionKey(processDefinitionKey);

        // 3: 基于流程定义 key 和 部署的id 查询 流程定义对象 => 只会查询到一个对象 => 获取版本号
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .processDefinitionKey(processDefinitionKey)
                .deploymentId(deployment.getId())
                .singleResult();

        if (processDefinition == null) {
            throw new ServiceException("流程定义 Key 不正确!!!");
        }

        // 4: 通过 "流程定义对象" 获取版本号
        bpmnInfo.setVersion(Long.parseLong(processDefinition.getVersion() + ""));

        // 5: 保存 bpmnInfo 对象
        super.save(bpmnInfo);

        // 6: 部署完成之后自动添加节点
        //流程定义id
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        Process process = bpmnModel.getProcesses().get(0);
        //获取所有节点
        Collection<FlowElement> flowElements = process.getFlowElements();

        for (FlowElement flowElement : flowElements) {
            // 节点对象(BpmnNode)
            BpmnNode bpmnNode = new BpmnNode();

            //假设是任务节点
            if(flowElement instanceof UserTask) {
                UserTask userTask = (UserTask)flowElement;

                // 任务的name == BpmnNode 对象的 desc(节点描述)
                String taskName = userTask.getName();
                bpmnNode.setNodeDesc(taskName);

                // 任务的id == BpmnNode 对象的 Key
                String id = userTask.getId();
                bpmnNode.setNodeKey(id);

                // 流程定义ID = BpmnNode 对象的 bpmnInfoId
                bpmnNode.setBpmnInfoId(bpmnInfo.getId());

                // 部署 流程文件 完成之后自动生成流程节点
                bpmnNodeService.save(bpmnNode);
            }
        }
    }

    /**
     * 基于 流程定义ID 查询流程定义对象
     *
     * @param id 流程定义ID
     * @return   流程定义对象
     */
    @Override
    public BpmnInfo getById(Serializable id) {
        BpmnInfo bpmnInfo = super.getById(id);
        if (bpmnInfo == null) {
            throw  new  ServiceException("参数错误, 流程信息不存在!!!");
        }
        return bpmnInfo;
    }
}
